/** create by liuhua at 2017年12月20日 上午9:21:44 **/
package com.star.truffle.core.util;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.star.truffle.core.KeyValue;
import com.star.truffle.core.security.dto.StarUserInfo;
import com.star.truffle.core.security.dto.StarUserInfoTemp;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MemoryCacheUtil {
  
  public enum CacheType {
    star_user_info(3000),
    yzm(1000);

    // 该类型的缓存key的数量
    private Integer size;

    private CacheType(Integer size) {
      this.size = size;
    }

    public Integer getSize() {
      return size;
    }

    public void setSize(Integer size) {
      this.size = size;
    }
  }

  private static int maximumSessions = 2;
  private static ObjectMapper mapper = new ObjectMapper();
  private static Map<String, Cache<String, StarUserInfo>> userInfos = new HashMap<>();
  
  static {
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  }
  public static void put(String business, String token, StarUserInfo userInfo) {
    Cache<String, StarUserInfo> cache = userInfos.get(business);
    if(null == cache) {
      cache = CacheBuilder.newBuilder().build();
      userInfos.put(business, cache);
    }
    long num = cache.asMap().entrySet().stream().filter(entry -> entry.getValue().getUsername().equals(userInfo.getUsername())).count();
    if(num >= maximumSessions) {
      List<String> keys = cache.asMap().entrySet().stream().filter(entry -> entry.getValue().getUsername().equals(userInfo.getUsername())).limit(maximumSessions - 1).map(entry -> entry.getKey()).collect(Collectors.toList());
      cache.invalidateAll(keys);
    }
    cache.put(token, userInfo);
    new Thread(() -> {
      Cache<String, StarUserInfo> temp = userInfos.get(business);
      Map<String, StarUserInfo> users = temp.asMap();
      try {
        String userInfos = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(users);
        File path = new File("/opt/cache/" + business);
        if(! path.exists()) {
          path.mkdirs();
        }
        File file = new File(path, "userinfos");
        if(! file.exists()) {
          file.createNewFile();
        }
        FileWriter writer = new FileWriter(file);
        writer.write(userInfos);
        writer.flush();
        writer.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }) .start();
  }
  
  public static Optional<StarUserInfo> get(String token) {
    String business = token.split("_")[0];
    Cache<String, StarUserInfo> cache = userInfos.get(business);
    if(null == cache) {
      synchronized (new byte[0]) {
        cache = userInfos.get(business);
        if(null == cache) {
          File file = new File("/opt/cache/" + business + "/userinfos");
          if(file.exists()) {
            try {
              FileReader reader = new FileReader(file);
              Map<String, StarUserInfoTemp> users = mapper.readValue(reader, new TypeReference<Map<String, StarUserInfoTemp>>() {});
              if(null != users && ! users.isEmpty()) {
                Map<String, StarUserInfo> temp = users.entrySet().stream().map(entry -> {
                  String key = entry.getKey();
                  StarUserInfo info = null != entry.getValue() ? entry.getValue().getUserInfo() : null;
                  return KeyValue.<String, StarUserInfo>builder().key(key).value(info).build();
                }).collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
                cache = CacheBuilder.newBuilder().build();
                cache.putAll(temp);
                userInfos.put(business, cache);
              }
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
      }
    }
    if(null != cache) {
      long exp = 1000L * 60 * 60 * 24 * 365;
      StarUserInfo userInfo = cache.getIfPresent(token);
      if (null != userInfo && userInfo.getLoginTime() + exp > System.currentTimeMillis()) {
        return Optional.ofNullable(userInfo);
      }
    }
    return Optional.ofNullable(null);
  }
  
  public static void remove(String token) {
    String business = token.split("_")[0];
    Cache<String, StarUserInfo> cache = userInfos.get(business);
    if(null != cache) {
      cache.invalidate(token);
    }
  }

  private static Cache<CacheType, Cache<Object, Object>> cache = CacheBuilder.newBuilder().maximumSize(1000).build();

  public static <T> void put(CacheType cacheType, Object key, T object) {
    put(cacheType, 1000 * 60 * 10, key, object);
  }

  public static <T> void put(CacheType cacheType, long timeMillis, Object key, T object) {
    try {
      Cache<Object, Object> map = cache.get(cacheType, new Callable<Cache<Object, Object>>() {
        public Cache<Object, Object> call() throws Exception {
          return CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(cacheType.getSize()).build();
        }
      });
      map.put(key, object);
    } catch (Exception e) {
      log.error("", e);
    }
  }
  
  @SuppressWarnings("unchecked")
  public static <T> T get(CacheType cacheType, Object key, TypeReference<T> typeReference) {
    try {
      Cache<Object, Object> map = cache.getIfPresent(cacheType);
      if (null == map || map.size() == 0) {
        cache.invalidate(cacheType);
        return null;
      }
      Object object = map.getIfPresent(key);
      if (null == object) {
        map.invalidate(key);
        log.info("从缓存获取数据------>" + cacheType.name() + "(" + key + ")，缓存数据已被清除");
      } else {
        log.info("从缓存获取数据------>" + cacheType.name() + "(" + key + ")，已从缓存取到数据");
      }
      map.cleanUp();
      return (T) object;
    } catch (Exception e) {
      log.error("", e);
    }
    return null;
  }

  public static Object get(CacheType cacheType, Object key) {
    try {
      Cache<Object, Object> map = cache.getIfPresent(cacheType);
      if (null == map || map.size() == 0) {
        cache.invalidate(cacheType);
        return null;
      }
      Object object = map.getIfPresent(key);
      return object;
    } catch (Exception e) {
      log.error("", e);
    }
    return null;
  }
}
